{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "N6ZDpd9XzFeN"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Hub Authors.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "KUu4vOt5zI9d"
      },
      "outputs": [],
      "source": [
        "# Copyright 2018 The TensorFlow Hub Authors. All Rights Reserved.\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     http://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License.\n",
        "# =============================================================================="
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ok9PfyoQ2rH_"
      },
      "source": [
        "# TF-Hub로 간단한 텍스트 분류자를 빌드하는 방법\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Kzz1rsXKNLww"
      },
      "source": [
        "> 참고: 이 튜토리얼에서는 **지원 중단된** TensorFlow 1 기능을 사용합니다. 이 작업에 대한 최신 접근 방식은 [TensorFlow 2 버전](https://www.tensorflow.org/hub/tutorials/tf2_text_classification)을 참조하세요.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/hub/tutorials/text_classification_with_tf_hub.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/hub/tutorials/text_classification_with_tf_hub.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub 소스 보기</a></td>\n",
        "  <td><a href=\"https://tfhub.dev/google/nnlm-en-dim128/1\"><img src=\"https://www.tensorflow.org/images/hub_logo_32px.png\">TF Hub 모델보기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AK3mz3JNMW8Y"
      },
      "source": [
        "TF-Hub는 재사용 가능한 리소스, 특히 사전 훈련된 **모듈** 형태로 머신러닝 전문 지식을 공유하는 플랫폼입니다. 이 튜토리얼은 크게 두 부분으로 구성되어 있습니다.\n",
        "\n",
        "**소개:** TF-Hub로 텍스트 분류자 훈련하기\n",
        "\n",
        "TF-Hub 텍스트 임베딩 모듈을 사용하여 합리적인 기준 정확성으로 간단한 감상 분류자를 훈련합니다. 그런 다음, 예측을 분석하여 모델이 합리적인지 확인하고 정확성을 높이기 위한 개선점을 제안합니다.\n",
        "\n",
        "**고급:** 전이 학습 분석\n",
        "\n",
        "이 섹션에서는 다양한 TF-Hub 모듈을 사용하여 추정기(estimator)의 정확성에 미치는 영향을 비교하고 전이 학습의 장점과 함정을 보여줍니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aYVd26q1_3xW"
      },
      "source": [
        "## 선택적 전제 조건\n",
        "\n",
        "- Tensorflow [사전 제작된 추정기 프레임워크](https://www.tensorflow.org/get_started/premade_estimators)에 대한 기본적 이해\n",
        "- [Pandas](https://pandas.pydata.org/) 라이브러리에 익숙\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xOATihhH1IxS"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "code",
        "id": "_8N3Hx2dyUC-"
      },
      "outputs": [],
      "source": [
        "# Install TF-Hub.\n",
        "!pip install seaborn"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tRXN9a8Mz8e-"
      },
      "source": [
        "Tensorflow 설치에 대한 자세한 정보는 [https://www.tensorflow.org/install/](https://www.tensorflow.org/install/)에서 찾을 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v7hy0bhngTUp"
      },
      "outputs": [],
      "source": [
        "from absl import logging\n",
        "\n",
        "import tensorflow as tf\n",
        "import tensorflow_hub as hub\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import pandas as pd\n",
        "import re\n",
        "import seaborn as sns"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6OPyVxHuiTEE"
      },
      "source": [
        "# 시작하기\n",
        "\n",
        "## 데이터\n",
        "\n",
        "[Large Movie Review Dataset v1.0](http://ai.stanford.edu/~amaas/data/sentiment/) 과제[(Mass 등, 2011)](http://ai.stanford.edu/~amaas/papers/wvSent_acl2011.pdf)의 해결을 시도할 것입니다. 이 데이터세트는 긍정성을 기준으로 1부터 10까지 분류된 IMDB 영화 리뷰로 구성됩니다. 리뷰를 **부정적** 또는 **긍정적**으로 분류하는 것이 과제입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "rKzc-fOGV72G"
      },
      "outputs": [],
      "source": [
        "# Load all files from a directory in a DataFrame.\n",
        "def load_directory_data(directory):\n",
        "  data = {}\n",
        "  data[\"sentence\"] = []\n",
        "  data[\"sentiment\"] = []\n",
        "  for file_path in os.listdir(directory):\n",
        "    with tf.io.gfile.GFile(os.path.join(directory, file_path), \"r\") as f:\n",
        "      data[\"sentence\"].append(f.read())\n",
        "      data[\"sentiment\"].append(re.match(\"\\d+_(\\d+)\\.txt\", file_path).group(1))\n",
        "  return pd.DataFrame.from_dict(data)\n",
        "\n",
        "# Merge positive and negative examples, add a polarity column and shuffle.\n",
        "def load_dataset(directory):\n",
        "  pos_df = load_directory_data(os.path.join(directory, \"pos\"))\n",
        "  neg_df = load_directory_data(os.path.join(directory, \"neg\"))\n",
        "  pos_df[\"polarity\"] = 1\n",
        "  neg_df[\"polarity\"] = 0\n",
        "  return pd.concat([pos_df, neg_df]).sample(frac=1).reset_index(drop=True)\n",
        "\n",
        "# Download and process the dataset files.\n",
        "def download_and_load_datasets(force_download=False):\n",
        "  dataset = tf.keras.utils.get_file(\n",
        "      fname=\"aclImdb.tar.gz\", \n",
        "      origin=\"http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz\", \n",
        "      extract=True)\n",
        "  \n",
        "  train_df = load_dataset(os.path.join(os.path.dirname(dataset), \n",
        "                                       \"aclImdb\", \"train\"))\n",
        "  test_df = load_dataset(os.path.join(os.path.dirname(dataset), \n",
        "                                      \"aclImdb\", \"test\"))\n",
        "  \n",
        "  return train_df, test_df\n",
        "\n",
        "# Reduce logging output.\n",
        "logging.set_verbosity(logging.ERROR)\n",
        "\n",
        "train_df, test_df = download_and_load_datasets()\n",
        "train_df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "D9Xq4x1mU3un"
      },
      "source": [
        "## 모델\n",
        "\n",
        "### 입력 함수\n",
        "\n",
        "[Estimator 프레임워크](https://www.tensorflow.org/get_started/premade_estimators#overview_of_programming_with_estimators)는 Pandas 데이터프레임을 래핑하는 [입력 함수](https://www.tensorflow.org/api_docs/python/tf/compat/v1/estimator/inputs/pandas_input_fn)를 제공합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "25rdoEHih0fm"
      },
      "outputs": [],
      "source": [
        "# Training input on the whole training set with no limit on training epochs.\n",
        "train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "    train_df, train_df[\"polarity\"], num_epochs=None, shuffle=True)\n",
        "\n",
        "# Prediction on the whole training set.\n",
        "predict_train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "    train_df, train_df[\"polarity\"], shuffle=False)\n",
        "# Prediction on the test set.\n",
        "predict_test_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "    test_df, test_df[\"polarity\"], shuffle=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Uyl6YGRcVAwP"
      },
      "source": [
        "### 특성 열\n",
        "\n",
        "TF-Hub는 주어진 텍스트 특성에 모듈을 적용하고 모듈의 출력을 추가적으로 전달하는 [특성 열](https://www.tensorflow.org/hub/api_docs/python/hub/text_embedding_column.md)을 제공합니다. 이 튜토리얼에서는 [nnlm-en-dim128 모듈](https://tfhub.dev/google/nnlm-en-dim128/1)을 사용합니다. 이 튜토리얼의 목적에 비추어 가장 중요한 사실은 다음과 같습니다.\n",
        "\n",
        "- 모듈은 **1-D 문자열 텐서의 문장 배치**를 입력으로 사용합니다.\n",
        "- 모듈은 **문장의 전처리**(예: 구두점 제거 및 공백 분할)를 담당합니다.\n",
        "- 모듈은 모든 입력에서 동작합니다(예: **nnlm-en-dim128**은 어휘에 없는 단어를 ~20.000개 버킷으로 해시 처리함)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "X7vyvj-hDEXu"
      },
      "outputs": [],
      "source": [
        "embedded_text_feature_column = hub.text_embedding_column(\n",
        "    key=\"sentence\", \n",
        "    module_spec=\"https://tfhub.dev/google/nnlm-en-dim128/1\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YPuHgx3BWBOg"
      },
      "source": [
        "### 추정기\n",
        "\n",
        "분류를 위해 [DNN 분류자](https://www.tensorflow.org/api_docs/python/tf/estimator/DNNClassifier)를 사용할 수 있습니다(튜토리얼 마지막에 있는 레이블 함수의 다른 모델링에 대한 추가 설명 참고)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "23U30yEkVq4w"
      },
      "outputs": [],
      "source": [
        "estimator = tf.estimator.DNNClassifier(\n",
        "    hidden_units=[500, 100],\n",
        "    feature_columns=[embedded_text_feature_column],\n",
        "    n_classes=2,\n",
        "    optimizer=tf.keras.optimizers.Adagrad(lr=0.003))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-O_k-8jgWPXY"
      },
      "source": [
        "### 훈련\n",
        "\n",
        "적절한 스텝 수만큼 추정기를 훈련합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "e5uDRv1r7Ed4"
      },
      "outputs": [],
      "source": [
        "# Training for 5,000 steps means 640,000 training examples with the default\n",
        "# batch size. This is roughly equivalent to 25 epochs since the training dataset\n",
        "# contains 25,000 examples.\n",
        "estimator.train(input_fn=train_input_fn, steps=5000);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s8j7YTRSe7Pj"
      },
      "source": [
        "# 예측\n",
        "\n",
        "훈련 및 테스트 세트 모두에 대한 예측을 실행합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zbLg5LzGwAfC"
      },
      "outputs": [],
      "source": [
        "train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn)\n",
        "test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn)\n",
        "\n",
        "print(\"Training set accuracy: {accuracy}\".format(**train_eval_result))\n",
        "print(\"Test set accuracy: {accuracy}\".format(**test_eval_result))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DR2IsTF5vuAX"
      },
      "source": [
        "## 혼동 행렬\n",
        "\n",
        "오분류 분포를 이해하기 위해 혼동 행렬을 시각적으로 확인할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nT71CtArpsKz"
      },
      "outputs": [],
      "source": [
        "def get_predictions(estimator, input_fn):\n",
        "  return [x[\"class_ids\"][0] for x in estimator.predict(input_fn=input_fn)]\n",
        "\n",
        "LABELS = [\n",
        "    \"negative\", \"positive\"\n",
        "]\n",
        "\n",
        "# Create a confusion matrix on training data.\n",
        "cm = tf.math.confusion_matrix(train_df[\"polarity\"], \n",
        "                              get_predictions(estimator, predict_train_input_fn))\n",
        "\n",
        "# Normalize the confusion matrix so that each row sums to 1.\n",
        "cm = tf.cast(cm, dtype=tf.float32)\n",
        "cm = cm / tf.math.reduce_sum(cm, axis=1)[:, np.newaxis]\n",
        "\n",
        "sns.heatmap(cm, annot=True, xticklabels=LABELS, yticklabels=LABELS);\n",
        "plt.xlabel(\"Predicted\");\n",
        "plt.ylabel(\"True\");"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sG-ES55Ftp-t"
      },
      "source": [
        "# 추가 개선\n",
        "\n",
        "1. **감상 회귀**: 분류자를 사용하여 각 예를 극성 클래스에 할당했습니다. 그러나 우리에게는 이용할 수 있는 또 다른 범주 특성인 감상이 있습니다. 여기서 클래스는 실제로 척도를 나타내며 기본 값(긍정적/부정적)은 연속 범위로 잘 매핑될 수 있습니다. 분류([DNN Classifier](https://www.tensorflow.org/api_docs/python/tf/contrib/learn/DNNClassifier)) 대신 회귀([DNN Regressor](https://www.tensorflow.org/api_docs/python/tf/contrib/learn/DNNRegressor))를 계산하여 이 속성을 사용할 수 있습니다.\n",
        "2. **더 큰 모듈**: 이 튜토리얼에서는 메모리 사용을 제한하기 위해 작은 모듈을 사용했습니다. 더 큰 어휘와 더 큰 임베딩 공간을 가진 모듈의 경우에는 정확성과 관련해 추가적으로 고려할 사항이 있을 수 있습니다.\n",
        "3. **매개변수 조정**: 특히 다른 모듈을 사용하는 경우에 학습률 또는 스텝 수와 같은 메타 매개변수를 조정하여 정확성을 향상할 수 있습니다. 테스트 세트로 잘 일반화하지 않고 훈련 데이터를 예측하는 방법을 배우는 모델을 설정하는 것이 매우 쉽기 때문에 검증 세트는 합리적인 결과를 얻으려는 경우에 매우 중요합니다.\n",
        "4. **더 복잡한 모델**: 여기서는 각 개별 단어를 포함한 다음 평균과 결합하여 문장 임베딩을 계산하는 모듈을 사용했습니다. 문장의 본질을 더 잘 포착하기 위해 순차 모듈(예: [Universal Sentence Encoder](https://tfhub.dev/google/universal-sentence-encoder/2) 모듈)을 사용할 수도 있습니다. 또는 두 개 이상의 TF-Hub 모듈을 조화롭게 사용할 수도 있습니다.\n",
        "5. **정규화**: 과대적합을 방지하기 위해 일종의 정규화를 수행하는 옵티마이저(예: [Proximal Adagrad Optimizer](https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/ProximalAdagradOptimizer))를 사용할 수 있습니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fKRNsaO8L50F"
      },
      "source": [
        "# 고급: 전이 학습 분석\n",
        "\n",
        "전이 학습을 사용하면 **훈련 리소스를 절약**하고 **작은 데이터세트에서 훈련**하는 경우에도 우수한 모델 일반화를 달성할 수 있습니다. 여기서는 두 가지 다른 TF-Hub 모듈로 훈련하는 예를 설명합니다.\n",
        "\n",
        "- **[nnlm-en-dim128](https://tfhub.dev/google/nnlm-en-dim128/1)** - 사전 훈련된 텍스트 임베딩 모듈\n",
        "- **[random-nnlm-en-dim128](https://tfhub.dev/google/random-nnlm-en-dim128/1)** - **nnlm-KO-dim128**과 같은 어휘와 네트워크를 가진 텍스트 임베딩 모듈이지만 가중치는 무작위로 초기화되고 실제 데이터에서 훈련되지 않습니다.\n",
        "\n",
        "그리고 두 가지 모드로 훈련합니다.\n",
        "\n",
        "- **분류자만** 훈련(예: 모듈 동결)\n",
        "- **모듈과 함께 분류자** 훈련\n",
        "\n",
        "다양한 모듈을 사용할 때 정확성에 어떤 영향을 미치는지 알아보기 위해 몇 가지 훈련과 평가를 실행해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AWYa1So1ARyz"
      },
      "outputs": [],
      "source": [
        "def train_and_evaluate_with_module(hub_module, train_module=False):\n",
        "  embedded_text_feature_column = hub.text_embedding_column(\n",
        "      key=\"sentence\", module_spec=hub_module, trainable=train_module)\n",
        "\n",
        "  estimator = tf.estimator.DNNClassifier(\n",
        "      hidden_units=[500, 100],\n",
        "      feature_columns=[embedded_text_feature_column],\n",
        "      n_classes=2,\n",
        "      optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.003))\n",
        "\n",
        "  estimator.train(input_fn=train_input_fn, steps=1000)\n",
        "\n",
        "  train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn)\n",
        "  test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn)\n",
        "\n",
        "  training_set_accuracy = train_eval_result[\"accuracy\"]\n",
        "  test_set_accuracy = test_eval_result[\"accuracy\"]\n",
        "\n",
        "  return {\n",
        "      \"Training accuracy\": training_set_accuracy,\n",
        "      \"Test accuracy\": test_set_accuracy\n",
        "  }\n",
        "\n",
        "\n",
        "results = {}\n",
        "results[\"nnlm-en-dim128\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/nnlm-en-dim128/1\")\n",
        "results[\"nnlm-en-dim128-with-module-training\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/nnlm-en-dim128/1\", True)\n",
        "results[\"random-nnlm-en-dim128\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/random-nnlm-en-dim128/1\")\n",
        "results[\"random-nnlm-en-dim128-with-module-training\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/random-nnlm-en-dim128/1\", True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CsWppYMphIPh"
      },
      "source": [
        "결과를 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "UVkdErEKkIXL"
      },
      "outputs": [],
      "source": [
        "pd.DataFrame.from_dict(results, orient=\"index\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Z9rZ2fuGfUFh"
      },
      "source": [
        "이미 일부 패턴을 볼 수 있지만 먼저 테스트 세트의 기준 정확성(가장 대표적인 클래스의 레이블만 출력하여 얻을 수 있는 하한)을 수립해야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IgYPVvc3G6OS"
      },
      "outputs": [],
      "source": [
        "estimator.evaluate(input_fn=predict_test_input_fn)[\"accuracy_baseline\"]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UN4D-DPPrINX"
      },
      "source": [
        "가장 대표적인 클래스를 지정하면 **50%**의 정확성을 얻을 수 있습니다. 여기서 몇 가지 주목해야 할 사항이 있습니다.\n",
        "\n",
        "1. 놀라울 수도 있겠지만, **고정된 무작위 임베딩이 아니어도 모델을 훈련할 수 있습니다**. 그 이유는 사전의 모든 단어가 무작위 벡터에 매핑되더라도 추정기는 완전히 연결된 레이어를 사용하여 공간을 분리할 수 있기 때문입니다.\n",
        "2. **무작위 임베딩**을 사용하여 모듈을 훈련할 수 있게 하면 분류자만 훈련할 때와 달리 훈련 및 테스트 정확성이 모두 향상됩니다.\n",
        "3. **사전 훈련된 임베딩**을 사용하여 모듈을 훈련하면 두 정확성이 모두 향상됩니다. 그러나 훈련 세트의 과대적합에 유의해야 합니다. 사전 훈련된 모듈을 훈련하는 것은 정규화를  적용하더라도 위험할 수 있는데, 그 이유는 임베딩 가중치가 더 이상 다양한 데이터에서 훈련된 언어 모델을 나타내지 않고, 대신 새 데이터세트의 이상적인 표현으로 수렴하기 때문입니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "N6ZDpd9XzFeN"
      ],
      "name": "text_classification_with_tf_hub.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
